using System;
using System.Collections.Generic;
using System.Text;
using Sedodream.Sudoku.Exceptions;

namespace Sedodream.Sudoku.Board
{
	public class SudokuBoard : IBoard
	{
		#region Fields
		private IBoardData boardData;
		private IDictionary<int, ICellGroup> columns;
		private IDictionary<int, ICellGroup> rows;
		private IDictionary<int, ICellGroup> squares;
		private ICell[,] cells;
		private BoardState state;

		private static int numBoardCreated = 0;
		#endregion

		#region Constructors
		private SudokuBoard()
		{
			numBoardCreated++;
		}
		public SudokuBoard(IBoardData boardData)
			: this()
		{
			if (boardData == null)
				throw new ArgumentNullException("boardData", "boardData cannot be null");

			this.boardData = new BoardData(boardData.Size);

			//this.boardData = boardData;

			cells = new ICell[boardData.SideLength, boardData.SideLength];
			for (int rowIndex = 0; rowIndex < boardData.SideLength; rowIndex++)
			{
				for (int colIndex = 0; colIndex < boardData.SideLength; colIndex++)
				{
					this.boardData[rowIndex, colIndex] = boardData[rowIndex, colIndex];
					ICell cell = new Cell(this, new Point(rowIndex, colIndex));
					cells[rowIndex, colIndex] = cell;
				}
			}

			this.columns = CreateColumnsFor(this, cells);
			this.rows = CreateRowsFor(this, cells);
			this.squares = CreateSquaresFor(this, cells);

			//this.boardData.BoardChanged +=new Sedodream.Sudoku.Events.BoardDataChanged(boardData_BoardChanged);
		}
		public SudokuBoard(IBoardData boardData, ICell[,] cells)
			: this(boardData)
		{
			//this.boardData = boardData;

			

			this.columns = CreateColumnsFor(this, cells);
			this.rows = CreateRowsFor(this, cells);
			this.squares = CreateSquaresFor(this, cells);


			//this.boardData.BoardChanged +=new Sedodream.Sudoku.Events.BoardDataChanged(boardData_BoardChanged);
		}
		#endregion

		#region ISudokuBoard Members
		public IBoardData BoardData
		{
			get { return this.boardData; }
		}
		public IDictionary<int, ICellGroup> Columns
		{
			get { return this.columns; }
		}
		public IDictionary<int, ICellGroup> Rows
		{
			get { return this.rows; }
		}
		public IDictionary<int, ICellGroup> Squares
		{
			get { return this.squares; }
		}
		public ICell[,] Cells
		{
			get { return this.cells; }
		}
		public ICell this[int row, int col]
		{
			get { return this.cells[row, col]; }
		}

		public BoardState State
		{
			get { return this.state; }
			set { this.state = value; }
		}
		public void UpdateBoardState()
		{
			//see if there are any duplicates in any row/column/square
			List<ICellGroup> allCellGroups = new List<ICellGroup>();
			allCellGroups.AddRange(this.Columns.Values);
			allCellGroups.AddRange(this.Rows.Values);
			allCellGroups.AddRange(this.Squares.Values);

			foreach (ICellGroup group in allCellGroups)
			{
				if (group == null)
					continue;
				IList<int> values = group.Values;
				//see if there are any duplicates in this list
				if (values == null || values.Count < 1)
					continue;
				IList<int> newList = new List<int>();
				foreach (int val in values)
				{
					if (newList.Contains(val))
					{
						this.state = BoardState.Failed;
						return;
					}
					newList.Add(val);
				}
			}
			bool hasEmptyCell = false;
			//see if there is some cell that has no AllowedNumbers but also no value
			foreach (ICell cell in this.Cells)
			{
				if (cell == null)
					continue;
				if (!cell.CellValue.HasValue)
					hasEmptyCell = true;
				cell.Update();
				if (!cell.CellValue.HasValue && cell.AllowedNumbers.Count <= 0 && cell.CellValue == null)
				{
					this.state = BoardState.Failed;
					return;
				}
			}

			if (hasEmptyCell)
			{
				this.state = BoardState.InProgress;
				return;
			}

			//now lets see if we have solved the board
			//List<int> allNumbers = new List<int>();
			//for (int i = 1; i < boardData.SideLength+1; i++)
			//{
			//    allNumbers.Add(i);
			//}

			foreach (ICellGroup group in allCellGroups)
			{
				if (group == null)
					continue;
				List<int> numbersInGroup = new List<int>();
				foreach (ICell cell in group.Cells)
				{
					if (numbersInGroup.Contains(cell.CellValue.Value))
					{
						string message = string.Format("Board may be in an invalid state. Current board\n{0}", this.ToString());
						throw new ApplicationException(message);
					}
					numbersInGroup.Add(cell.CellValue.Value);
				}
				if (numbersInGroup.Count < this.BoardData.SideLength)
				{
					State = BoardState.InProgress;
					return;
				}
			}


			this.state = BoardState.Solved;
		}
		#endregion

		public override string ToString()
		{
			if (BoardData == null)
				return "[]";
			return BoardData.ToString();
		}

		void boardData_BoardChanged(object sender, Sedodream.Sudoku.Events.BoardChangedEventArgs args)
		{
			int debug = 3;
			debug = 33;
			UpdateBoardState();
			//throw new Exception("The method or operation is not implemented.");
		}

		public static IDictionary<int, ICellGroup> CreateColumnsFor(IBoard board, ICell[,] cells)
		{
			IDictionary<int, ICellGroup> cols = new Dictionary<int, ICellGroup>();

			for (int colIndex = 0; colIndex < board.BoardData.SideLength; colIndex++)
			{
				IList<ICell> colCells = new List<ICell>();
				for (int rowIndex = 0; rowIndex < board.BoardData.SideLength; rowIndex++)
				{
					ICell cell = new Cell(board, new Point(rowIndex, colIndex));
					colCells.Add(cell);
				}
				ICellGroup colGroup = new CellGroup(CellGroupType.Column,
					colIndex, board.BoardData, colCells);
				cols.Add(colIndex, colGroup);
			}
			return cols;
		}
		public static IDictionary<int, ICellGroup> CreateRowsFor(IBoard board, ICell[,] cells)
		{
			IDictionary<int, ICellGroup> rows = new Dictionary<int, ICellGroup>();

			for (int rowIndex = 0; rowIndex < board.BoardData.SideLength; rowIndex++)
			{
				IList<ICell> rowCells = new List<ICell>();
				for (int colIndex = 0; colIndex < board.BoardData.SideLength; colIndex++)
				{
					ICell cell = new Cell(board, new Point(rowIndex, colIndex));
					rowCells.Add(cell);
				}
				ICellGroup rowGroup = new CellGroup(CellGroupType.Row, rowIndex, board.BoardData, rowCells);
				rows.Add(rowIndex, rowGroup);
			}
			return rows;
		}
		public static IDictionary<int, ICellGroup> CreateSquaresFor(IBoard board, ICell[,] cells)
		{
			IDictionary<int, ICellGroup> squares = new Dictionary<int, ICellGroup>();
			for (int squareIndex = 0; squareIndex < board.BoardData.SideLength; squareIndex++)
			{
				if (squareIndex == 5)
				{
					int debugIndex = 3;
				}
				int size = board.BoardData.Size;
				IList<ICell> squareCells = new List<ICell>();
				int startRow = (int)Math.Floor((decimal)squareIndex / size) * size;
				for (int rowIndex = 0; rowIndex < board.BoardData.Size; rowIndex++)
				{
					//IList<ICell> currentRow = new List<ICell>();
					int startCol = (int)Math.Floor((decimal)squareIndex % size) * size;

					for (int colIndex = 0; colIndex < board.BoardData.Size; colIndex++)
					{
						//currentRow.Add(new Cell(boardData, new Point(startRow + rowIndex, startCol + colIndex)));
						squareCells.Add(new Cell(board, new Point(startRow + rowIndex, startCol + colIndex)));
					}
				}
				ICellGroup newSquare = new CellGroup(CellGroupType.Square, squareIndex, board.BoardData, squareCells);
				squares.Add(squareIndex, newSquare);
			}
			return squares;
		}

		public IList<ICellGroup> FindAllGroupsContaining(IPoint location)
		{
			//TODO: THIS CAN TOTALLY BE OPTIMIZED BASED ON GROUP TYPE/INDEX
			IList<ICellGroup> groups = new List<ICellGroup>();

			//TODO: FIGURE OUT HOW THESE ARE GETTING TO BE NULL IN THE FIRST PLACE
			if (this.Columns == null)
				this.columns = CreateColumnsFor(this, this.Cells);
			if (this.Rows == null)
				this.rows = CreateRowsFor(this, this.Cells);
			if (this.Squares == null)
				this.squares = CreateSquaresFor(this, this.Cells);

			foreach (ICellGroup group in Columns.Values)
			{
				if (group == null)
					continue;
				bool addThisGroup = false;
				foreach (ICell cell in group.Cells)
				{
					if (cell == null)
						continue;
					if (cell.Location.Equals(location))
					{
						addThisGroup = true;
						continue;
					}
				}
				if (addThisGroup)
				{
					groups.Add(group);
					break;
				}
			}
			foreach (ICellGroup group in Rows.Values)
			{
				if (group == null)
					continue;
				bool addThisGroup = false;
				foreach (ICell cell in group.Cells)
				{
					if (cell == null)
						continue;
					if (cell.Location.Equals(location))
					{
						addThisGroup = true;
						break;
					}
				}
				if (addThisGroup)
				{
					groups.Add(group);
					break;
				}
			}
			foreach (ICellGroup group in Squares.Values)
			{
				if (group == null)
					continue;
				bool addThisGroup = false;
				foreach (ICell cell in group.Cells)
				{
					if (cell == null)
						continue;
					if (cell.Location.Equals(location))
					{
						addThisGroup = true;
						break;
					}
				}
				if (addThisGroup)
				{
					groups.Add(group);
					break;
				}
			}

			return groups;
		}
		public void SetValueAt(IPoint location, int? value)
		{
			if (location == null)
				throw new ArgumentNullException("location");
			if (value <= 0)
			{
				string message = string.Format("Invalid value provided for cell. Value: {0}", value);
				throw new InvalidValueProvidedException(message);
			}
			if (value.HasValue)
			{
				if (this[location.Row, location.Col].CellValue != null)
				{
					string message = string.Format("Trying to override value");
					throw new ApplicationException(message);
				}
				if (!IsValueValid(location, value.Value))
				{
					this.state = BoardState.Failed;
					string message = string.Format("value [{0}] at location {1} is invalid", value.Value, location);
					//TODO: WHY DOES IT GET IN HERE?
					//throw new InvalidValueProvidedException(message);
				}
			}
			this.BoardData.SetValueAt(location.Row, location.Col, value);
			//foreach (ICell cell in this.Cells)
			//{
			//    if (cell == null)
			//        continue;
			//    if (cell.Location.Row == location.Row
			//        && cell.Location.Col == location.Col)
			//    {
			//        int debug = 3;
			//    }
			//}
		}
		public bool IsValueValid(IPoint location, int value)
		{
			if (location == null)
				throw new ArgumentNullException("location");

			//Get the ICell at this point
			ICell cell = this[location.Row, location.Col];
			if (cell.CellValue.HasValue)
				return false;

			cell.Update();
			IList<int> allowedNumbers = cell.AllowedNumbers;

			return allowedNumbers.Contains(value);
		}
		public bool IsMoveValid(IMove move)
		{
			if (move == null)
				throw new ArgumentNullException("move");

			return IsValueValid(move.Location, move.Value);
		}
		public List<IMove> FindAllMoves()
		{
			IPoint debugPoint = new Point(3, 6);

			List<IMove> moves = new List<IMove>();
			foreach (ICell cell in Cells)
			{
				if (cell == null || cell.CellValue.HasValue) 
					continue;
				if (cell.Location.Equals(debugPoint))
				{
					int debug = 3;
					cell.Update();
				}
				int numBefore = cell.AllowedNumbers.Count;
				cell.Update();
				int numAfter = cell.AllowedNumbers.Count;
				if (numAfter != numBefore)
				{
					string debug = "Update required";
				}
				//create one move per AllowedNumber
				foreach (int number in cell.AllowedNumbers)
				{
					IMove newMove = new Move(this, cell.Location, number);
					newMove.MoveScoreOLD = cell.CellComplexity!=null?cell.CellComplexity.Value:int.MaxValue;
					newMove.ForcedMove = (cell.CellComplexity == 1);
					moves.Add(newMove);
				}
			}
			moves.Sort(CompareMoves);
			return moves;
		}

		/// <summary>
		/// This can be used to compare moves.
		/// If either move is <c>null</c> then an <c>ArgumentException</c> will be thrown.
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns></returns>
		private static int CompareMoves(IMove x, IMove y)
		{
			if (x == null || y == null)
			{
				throw new ArgumentException("Move supplied for comparision is null");
			}

			int xScore = x.MoveScoreOLD;
			int yScore = y.MoveScoreOLD;
			if (xScore == yScore)
			{
				return 0;
			}
			else if (xScore < yScore)
			{
				return -1;
			}
			else
			{
				return 1;
			}
		}
	}
}